#include <string.h>
#include <stdlib.h>
#include "tp_debug.h"
#include "scps.h"
#include "scpstp.h"
#include "scpsudp.h"
#include <stdio.h>    /* temporary  - for udp_WriteTo() */

#include "scps_ip.h"

int scps_np_get_template (scps_np_rqts * rqts, scps_np_template * templ);


extern uint32_t tp_now;

/*
 * Close a UDP socket
 */
int udp_Close (int sockid)
{
  udp_Socket *s = (udp_Socket *) scheduler.sockets[sockid].ptr;

  if (s->myport == 0) {
    SET_ERR (SCPS_EBADF);
    return (-1);
  }
  udp_Unthread (s);
  return (0);
}

/*
 * Try to send the UDP datagram. Enqueue at most 
 * one UDP datagram if there's room, and then try 
 * to send it. Return the number of bytes queued. 
 * Otherwise, throw away the data, return -1 and 
 * set EWOULDBLOCK for non-blocking, or just block.
 *
 * This will need to be modified to provide a way 
 * for the application to flush the one-datagram 
 * buffer. timer? "system call"? later
 */
int udp_WriteTo (int sockid, byte * dp, int len, uint32_t ina, uint16_t port)
{
  uint32_t bytes_sent = 0;
  int cc = 0;
  struct mbuff *mbuffer;
  struct mbcluster *mbcluster;
  udp_Socket *s = (udp_Socket *) scheduler.sockets[sockid].ptr;

  if (s->myport == 0)    /* minimal error checking */
    {
      SET_ERR (SCPS_EBADF);
      goto cleanup;
      return (-1);
    }

  if (len > MAX_UDP_PAYLOAD)  /* enforce max UDP datagram size ??????? */
    {
      SET_ERR (SCPS_EMSGSIZE);
      goto cleanup;
      return (-1);
    }

  if ((s->rt_route->MTU) &&  /* enforce MTU */
      ((len + UDP_HDR_LEN + s->np_size + s->sp_size) > s->rt_route->MTU))
    {
      SET_ERR (SCPS_EMSGSIZE);
      goto cleanup;
      return (-1);
    }
    
  if ((s->rt_route && s->rt_route->flags & RT_LINK_AVAIL) &&
      (s->rt_route->current_credit >=
       ((int) s->send_buff->start->m_ext.len +
  UDP_HDR_LEN + s->sp_size + 20)))  /* hard-coded for IP! */
    {
      s->buff_full = FALSE;
    }

  if (s->buff_full)
    {
      SET_ERR (SCPS_ENOBUFS);
      goto cleanup;
      return (-1);
    }

  if ((cb_cpdatin (s->app_sbuff, (caddr_t)dp, len, 0, 0)) != len)
    {
      SET_ERR (SCPS_ENOBUFS);
      goto cleanup;
      return (-1);
    }
  s->ph.nl_head.ipv4.dst = ina;
  s->hisport = port;
  s->his_ipv4_addr = htonl ((uint32_t) ina);

  /* Build the packet! */

  if ((mcput (s->send_buff->start, s->app_sbuff->start,
        s->app_sbuff->read_off, len, 1)) != len)
    {
      LOG ("\nbigs problems in udp_WriteTo()\n");
      fflush (stdout);
      return (-1);
    }

  /* Fill in the requirements structure */
  s->np_rqts.tpid = IPPROTO_UDP;
  s->np_rqts.ipv4_dst_addr = htonl (ina);
  s->np_rqts.ipv4_src_addr = ntohl (local_addr);
  s->np_rqts.timestamp.format = 0;
  s->np_rqts.timestamp.ts_val[0] =
  s->np_rqts.timestamp.ts_val[1] = 0;
  /* s->np_rqts.bqos.precedence = rqts->bqos.precedence; */
  s->np_rqts.bqos.routing = 0;
  s->np_rqts.bqos.pro_specific = 0;
  s->np_rqts.eqos.ip_precedence = 0;
  s->np_rqts.eqos.ip_tos = 0;
  s->np_rqts.cksum = 0;
  s->np_rqts.int_del = 0;
  s->np_rqts.nl_protocol = NL_DEFAULT;


  switch (s->np_rqts.nl_protocol) { 
  case NL_PROTOCOL_IPV4:
    s->np_size = ip_get_template (&(s->np_rqts), &(s->ip_templ));
    break;

  case NL_PROTOCOL_NP:
    s->np_size = scps_np_get_template (&(s->np_rqts), &(s->np_templ));
    break;
  }

  udp_BuildHdr (s, s->send_buff->start);

  s->app_sbuff->size -= len;  /* size should be zero now! */
  s->buff_full = TRUE;

  if ((s->rt_route && s->rt_route->flags & RT_LINK_AVAIL) &&
      (s->rt_route->current_credit >=
       ((int) s->send_buff->start->m_ext.len + UDP_HDR_LEN + s->sp_size + 20)))  /* hard-coded for IP! */
    {
      cc = udp_Coalesce (s, &bytes_sent);
      bytes_sent = len;
      s->buff_full = FALSE;
      /* Let them know how much data we actually wrote to the transport */
      if (cc > 0)
        s->user_data += len;
    }

cleanup:

  /*
     * This all looks kind of crusty 
   */
  /* dequeue all clusters */
  for (mbcluster = deq_mclus (s->app_sbuff); mbcluster;
       mbcluster = deq_mclus (s->app_sbuff))
    free_mclus (mbcluster);

  s->app_sbuff->read_head = s->app_sbuff->write_head = NULL;
  s->app_sbuff->write_off = s->app_sbuff->bytes_beyond = 0;
  mbuffer = deq_mbuff (s->send_buff);
  free_mbuff (mbuffer);    /* free mbuff and all clusters */

  if (!(mbuffer = alloc_mbuff (MT_HEADER)))  /* get back our mbuff */
    {
      SET_ERR (SCPS_ENOMEM);
      exit (-1);    /* not the thing to do */
    }
  if (!(enq_mbuff (mbuffer, s->send_buff)))
    LOG ("MBUFFER ENQUEUEING ERROR in udp_Coalesce()\n");
  if (cc)
    return (len);
  else {
    SET_ERR (SCPS_ENOBUFS);
    return (-1);
  }
}

int udp_Read (int sockid, caddr_t data, int size)
{
  int read;
  struct mbuff *mbuffer;
  udp_Socket *sock = (udp_Socket *) scheduler.sockets[sockid].ptr;

  if (sock->myport == 0)  /* minimal error checking */
    {
      SET_ERR (SCPS_EBADF);
      return (-1);
    }

  /* If there is no mbuff and associated data available, then we block */

  if (!(mbuffer = sock->receive_buff->start))
    {
      ((tp_Socket *) scheduler.sockets[sockid].ptr)->thread->status = Blocked;
      scheduler.num_runable--;
      scheduler.sockets[sockid].read = 1;
      mbuffer = sock->receive_buff->start;
    }

  /*
   * Make sure the size request is less than or equal 
   * to the amount of memory we have available to this connection
   */

  if (size > mbuffer->m_ext.len)
    size = mbuffer->m_ext.len;

  /*
   * At this point, it should just be a matter 
   * of doing a cb_cpdatout() into the data pointer...
   */

  read = cb_cpdatout (sock->app_rbuff, data, size);


  if (read == 0)
    {
      LOG ("cb_cpdatout returns 0\t");
      LOG ("size = %d, app rbuff size = %d\n", size, (int) sock->app_rbuff->size);
      SET_ERR (SCPS_EWOULDBLOCK);
      return (-1);
    }

  /* Trim off the associated mbuff from the receive-buffer.   */

  /*  mb_rtrim (sock->receive_buff, read); */
  mbuffer = deq_mbuff (sock->receive_buff);
  free_mbuff (mbuffer);
  return (read);
}

int scps_recvfrom (int sockid, void *data, int size, void *ina, int *ina_len)
{
  int read;
  udp_Header *up;
  in_Header *ip;
  udp_Socket *sock = (udp_Socket *) scheduler.sockets[sockid].ptr;

  if ((sock->myport == 0) || (sock->Initialized != IPPROTO_UDP))
    {        /* minimal error checking */
      SET_ERR (SCPS_EBADF);
      return (-1);
    }

  /*
   * Make sure the size request is less than or equal 
   * to the amount of memory we have available to this 
   * connection
   */
  if (size > sock->app_rbuff->max_size)
    size = sock->app_rbuff->max_size;
  if (size > MAX_UDP_PAYLOAD)
    size = MAX_UDP_PAYLOAD;

  if (sock->app_rbuff->size)
    {
      /* this is how we enforce segment boundaries on reads */
      if (size > sock->receive_buff->start->m_ext.len)
  size = sock->receive_buff->start->m_ext.len;
    }
  else
    {
      SET_ERR (SCPS_EWOULDBLOCK);  /* nothing here to read now */
      return (-1);
    }

  /*
   * At this point, it should just be a matter of doing a cb_cpdatout()
   * into the data pointer...
   */

  read = cb_cpdatout (sock->app_rbuff, data, size);

  if (read == 0)
    {
      LOG ("cb_cpdatout returns 0\t");
      SET_ERR (SCPS_EWOULDBLOCK);
      return (-1);
    }

  /* Grab the sender's address from the mbuff... */
  
  ip = (in_Header *) (sock->receive_buff->start->m_pktdat);
  up = (udp_Header *) ((uint8_t *)ip + inv4_GetHdrlenBytes (ip));
  memcpy (&(((struct sockaddr_in *) ina)->sin_port), &(up->srcPort), sizeof (unsigned int));
  memcpy (&(((struct sockaddr_in *) ina)->sin_addr), &(ip->nl_head.ipv4.source), sizeof (uint32_t));

  /* Trim off the associated mbuff from the receive-buffer.   */
  mb_rtrim (sock->receive_buff, read);

  return (read);
}
